package com.lifesting.tool.j2as3.visitor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;

import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.QualifiedType;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.core.BinaryType;

public class AnalysisVisitor extends ASTVisitor {

	public static class ImportDescriptor {
		private int imported;
		private String className;
		private List<String> javaFiles = new ArrayList<String>();

		public int getImported() {
			return imported;
		}

		public void setImported(int imported) {
			this.imported = imported;
		}

		public String getClassName() {
			return className;
		}

		public void setClassName(String className) {
			this.className = className;
		}

		public void addJavaFile(String javaFile) {
			if (!javaFiles.contains(javaFile))
				javaFiles.add(javaFile);
		}

		public List<String> getJavaFiles() {
			return javaFiles;
		}

		public ImportDescriptor getCopy() {
			ImportDescriptor d = new ImportDescriptor();
			d.setImported(imported);
			d.setClassName(className);
			d.javaFiles.addAll(javaFiles);
			return d;
		}

		public ImportDescriptor() {
			super();
		}

		public ImportDescriptor(int imported, String className, String javaFile) {
			super();
			this.imported = imported;
			this.className = className;
			this.javaFiles.add(javaFile);
		}

	}
	class ConstructorDesc{
		String qn;
		int num;
		public ConstructorDesc(String qn, int num) {
			super();
			this.qn = qn;
			this.num = num;
		}
	}
	public HashMap<String, ImportDescriptor> outerLibrary = new HashMap<String, ImportDescriptor>();
	private static String[] EMPTY = new String[0];
	private String[] noNeedCount = EMPTY;
	private String typeName;
	private CompilationUnit unit;

	
	


	/**
	 * all outer definitions sorted by class name.
	 * 
	 * @return definitions
	 */
	public ImportDescriptor[] getAllOuterDefinitions() {
		ImportDescriptor[] entries = new ImportDescriptor[outerLibrary.size()];
		for (String s : outerLibrary.keySet()) {
			insert(entries, outerLibrary.get(s));
		}
		return entries;
	}
	

	private void insert(ImportDescriptor[] entries, ImportDescriptor desc) {
		int point = 0;
		for (int n = 0; n < entries.length; n++) {
			if (entries[n] == null || entries[n].getClassName().compareTo(desc.getClassName()) < 0) {
				point = n;
				break;
			}
		}
		for (int k = entries.length - 2; k >= point; k--) {
			entries[k + 1] = entries[k];
		}
		entries[point] = desc.getCopy();
	}

	public void reset() {
		noNeedCount = EMPTY;
		outerLibrary.clear();
	}

	@Override
	public boolean visit(QualifiedType node) {
		countIfQualifiedType(node);
		return false;
	}

	@Override
	public boolean visit(SimpleType node) {
		countIfJavaLangType(node);
		return false;
	}

	@Override
	public boolean visit(QualifiedName node) {
		IBinding type_binding = node.getQualifier().resolveBinding();
		// only care type.type.Type.variable
		if (type_binding != null && type_binding.getKind() == IBinding.VARIABLE) {
			return true;
		} else {
			if (type_binding == null || type_binding.getKind() == IBinding.PACKAGE)
				countImport(node.getFullyQualifiedName());
			else if (type_binding.getKind() == IBinding.TYPE) { // a.b.C.v
				// Only count class file,
				IType javaElement = (IType) type_binding.getJavaElement();
				if (javaElement instanceof BinaryType) {
					String qn = javaElement.getFullyQualifiedName();
					boolean counted = countImport(qn) || countIfJavaLangType(qn);
				}
			}
			return false;
		}
	}

	@Override
	public boolean visit(ImportDeclaration node) {
		String imported = node.getName().getFullyQualifiedName();
		countImport(imported);
		return false;
	}

	private void countIfQualifiedType(Type type) {
		if (type.isQualifiedType())
			countImport(((QualifiedType) type).getName().getFullyQualifiedName());
		countIfJavaLangType(type);
	}

	private boolean countImport(String imported) {
		boolean counted;
		if ((counted = needCount(imported))) {
			ImportDescriptor import_desc = outerLibrary.get(imported);
			String path = unit.getJavaElement().getResource().getFullPath().removeFirstSegments(2)
					.toString();
			if (null == import_desc) {
				import_desc = new ImportDescriptor(1, imported, path);
			} else {
				import_desc.setImported(import_desc.getImported() + 1);
				import_desc.addJavaFile(path);
			}
			outerLibrary.put(imported, import_desc);
		}
		return counted;

	}

	private void countIfJavaLangType(Type type) {
		ITypeBinding binding = type.resolveBinding();
		String full_name = binding.getQualifiedName();
		countIfJavaLangType(full_name);
	}

	private boolean countIfJavaLangType(String typeName) {
		boolean counted;
		if ((counted = typeName.startsWith("java.lang"))) {
			ImportDescriptor desritor = outerLibrary.get(typeName);
			String unit_name = unit.getJavaElement().getResource().getFullPath()
					.removeFirstSegments(2).toString();
			if (desritor == null) {
				desritor = new ImportDescriptor(1, typeName, unit_name);
			} else {
				if (!desritor.getJavaFiles().contains(unit_name)) {
					desritor.setImported(desritor.getImported() + 1);
					desritor.addJavaFile(unit_name);
				}
			}
			outerLibrary.put(typeName, desritor);
		}
		return counted;
	}

	private boolean needCount(String imported) {
		for (String s : noNeedCount) {
			if (imported.startsWith(s)) {
				return false;
			}
		}
		return true;
	}

	@Override
	public void endVisit(CompilationUnit node) {
		unit = null;
	}

	@Override
	public boolean visit(CompilationUnit node) {
		unit = node;
		return true;
	}

	public void setNoNeedCount(String[] array) {
		noNeedCount = array;
	}

}
